package howe.sudoku.blogic;

import howe.sudoku.entity.Puzzle;
import howe.sudoku.entity.SudokuInfoVo;
import howe.sudoku.names.SudokuName;
import howe.sudoku.util.CheckUtils;
import android.os.Bundle;
import android.os.SystemClock;
import android.util.Log;

public class PuzzleBL {

    private Puzzle puzzle = new Puzzle();

    private Puzzle readOnlyPuzzle = new Puzzle();
    private long mId;
    private long mCreated;
    private int mState;
    private long mLastPlayed;
    private String mNote;
    private long mTime;

    // Time when current activity has become active.
    private long mActiveFromTime = -1;

    public PuzzleBL() {
        mTime = 0;
        mLastPlayed = 0;
        mCreated = 0;
        mState = SudokuName.GAME_STATE_NOT_STARTED;
    }

    public Puzzle getPuzzle() {
        return puzzle;
    }

    public void setPuzzle(Puzzle puzzle) {
        this.puzzle = puzzle;
    }

    public Puzzle getReadOnlyPuzzle() {
        return readOnlyPuzzle;
    }

    /**
     * Sets time of play in milliseconds.
     * @param time
     */
    public void setTime(long time) {
        mTime = time;
    }

    public long getId() {
        return mId;
    }

    public void setId(long mId) {
        this.mId = mId;
    }

    /**
     * Gets time of game-play in milliseconds.
     * @return
     */
    public long getTime() {
        if (mActiveFromTime != -1) {
            return mTime + SystemClock.uptimeMillis() - mActiveFromTime;
        } else {
            return mTime;
        }
    }

    public void setCreated(long created) {
        mCreated = created;
    }

    public long getCreated() {
        return mCreated;
    }

    public void setState(int state) {
        mState = state;
    }

    public int getState() {
        return mState;
    }

    public void setLastPlayed(long lastPlayed) {
        mLastPlayed = lastPlayed;
    }

    public long getLastPlayed() {
        return mLastPlayed;
    }

    public static PuzzleBL createEmptyGame() {
        PuzzleBL game = new PuzzleBL();
        // set creation time
        game.setCreated(System.currentTimeMillis());
        return game;
    }

    public String getmNote() {
        return mNote;
    }

    public void setmNote(String mNote) {
        this.mNote = mNote;
    }

    public void createGame(SudokuInfoVo item) {
        if (CheckUtils.checkRequired(item.getId())) {
            mId = Long.valueOf(item.getId());
        } else {
            mId = -1;
        }
        mNote = item.getNote();
        if (CheckUtils.checkRequired(item.getCreated())) {
            mCreated = Long.valueOf(item.getCreated());
        } else {
            mCreated = -1;
        }
        if (CheckUtils.checkRequired(item.getState())) {
            mState = Integer.valueOf(item.getState());
        } else {
            mState = -1;
        }
        if (CheckUtils.checkRequired(item.getTime())) {
            mTime = Long.valueOf(item.getTime());
        } else {
            mTime = -1;
        }
        if (CheckUtils.checkRequired(item.getLastPlayed())) {
            mLastPlayed = Long.valueOf(item.getLastPlayed());
        } else {
            mLastPlayed = -1;
        }
        creatPuzzleByString(item.getData());

        if (CheckUtils.checkRequired(item.getReadOnlyData())) {
            creatReadOnlyPuzzleByString(item.getReadOnlyData());
        }
    }

    /**
     * Start game-play.
     */
    public void start() {
        mState = SudokuName.GAME_STATE_PLAYING;
        resume();
    }

    public void resume() {
        // reset time we have spent playing so far, so time when activity was not active
        // will not be part of the game play time
        mActiveFromTime = SystemClock.uptimeMillis();
    }

    /**
     * Pauses game-play (for example if activity pauses).
     */
    public void pause() {
        // save time we have spent playing so far - it will be reseted after resuming
        mTime += SystemClock.uptimeMillis() - mActiveFromTime;
        mActiveFromTime = -1;
        setLastPlayed(System.currentTimeMillis());
    }

    /**
     * Finishes game-play. Called when puzzle is solved.
     */
    public void finish() {
        pause();
        mState = SudokuName.GAME_STATE_COMPLETED;
    }

    /**
     * Resets game.
     */
    public void reset() {
        setTime(0);
        setLastPlayed(0);
        mState = SudokuName.GAME_STATE_NOT_STARTED;
    }

    public void saveState(Bundle outState) {
        outState.putLong("id", mId);
        outState.putString("note", mNote);
        outState.putLong("created", mCreated);
        outState.putInt("state", mState);
        outState.putLong("time", mTime);
        outState.putLong("lastPlayed", mLastPlayed);
        outState.putString("puzzle", toPuzzleString());
        outState.putString("readOnlyPuzzle", toReadOnlyPuzzleString());
    }

    public void restoreState(Bundle inState) {
        mId = inState.getLong("id");
        mNote = inState.getString("note");
        mCreated = inState.getLong("created");
        mState = inState.getInt("state");
        mTime = inState.getLong("time");
        mLastPlayed = inState.getLong("lastPlayed");
        creatPuzzleByString(inState.getString("puzzle"));
        creatReadOnlyPuzzleByString(inState.getString("readOnlyPuzzle"));
    }

    /**
     * Creat a puzzle array by string
     * @param string
     */
    public void creatPuzzleByString(String strPuz) {
        int iLength = 0;
        if (strPuz != null) {
            iLength = strPuz.length();
        }

        int[] iPuz = new int[iLength];

        for (int i = 0; i < iLength; i++) {
            iPuz[i] = strPuz.charAt(i) - '0' ;
        }
        this.puzzle.setPuzzle(iPuz);
    }


    /**
     * Creat a read only puzzle array by string
     * @param string
     */
    public void creatReadOnlyPuzzleByString(String strPuz) {
        int iLength = 0;
        if (strPuz != null) {
            iLength = strPuz.length();
        }

        int[] iROPuz = new int[iLength];

        for (int i = 0; i < iLength; i++) {
            iROPuz[i] = strPuz.charAt(i) - '0' ;
        }
        this.readOnlyPuzzle.setPuzzle(iROPuz);
    }

    /**
     * Calculate used tiles
     * @return success flag
     */
    public boolean calculateUsedTiles() {
        boolean ret = true;
        for (int x = 0; x < 9; x++) {
            for (int y = 0; y < 9; y++) {
                this.puzzle.getUsed()[x][y] = calculateUsedTiles(x, y);
                Log.d(SudokuName.PUZZLE,
                      "used[" + x + "][" + y + "] = " + toPuzzleString(this.puzzle.getUsed()[x][y]));

                if (0 == getTile(x, y)) {
                    ret = false;
                }
            }
        }
        return ret;
    }

    /**
     * Get used tiles by coordinate
     * @param x
     * @param y
     * @return used tiles
     */
    public int[] getUsedTiles(int x, int y) {
        return this.puzzle.getUsed()[x][y];
    }


    /**
     * Set tile if valid
     * @param x
     * @param y
     * @param value
     * @return set result(true:success false:failed)
     */
    public boolean setTileIfValid(int x, int y, int value) {
        int tiles[] = getUsedTiles(x, y);

        if (value != 0) {
            for (int tile : tiles) {
                if (tile == value) {
                    return false;
                }
            }
        }

        setTile(x,
                y,
                value);

        return true;
    }

    /**
     * Get tile by coordinate
     * @param x
     * @param y
     * @return
     */
    public String getTileString(int x, int y) {
        int v = getTile(x, y);
        if (v == 0) {
            return "" ;
        } else {
            return String.valueOf(v);
        }
    }

    /**
     * Get read only tile by coordinate
     * @param x
     * @param y
     * @return
     */
    public String getReadOnlyTileString(int x, int y) {
        int v = getReadOnlyTile(x, y);
        if (v == 0) {
            return "" ;
        } else {
            return String.valueOf(v);
        }
    }

    /**
     * Convert puzzle array to string
     * @return puzzle string
     */
    public String toPuzzleString() {
        StringBuilder buf = new StringBuilder();
        for (int element : this.puzzle.getPuzzle()) {
            buf.append(element);
        }
        return buf.toString();
    }

    /**
     * Convert read only puzzle array to string
     * @return puzzle string
     */
    public String toReadOnlyPuzzleString() {
        StringBuilder buf = new StringBuilder();
        for (int element : this.readOnlyPuzzle.getPuzzle()) {
            buf.append(element);
        }
        return buf.toString();
    }

    /**
     * Convert puzzle array to string by input array
     * @return puzzle string
     */
    public String toPuzzleString(int[] puz) {
        StringBuilder buf = new StringBuilder();
        for (int element : puz) {
            buf.append(element);
        }
        return buf.toString();
    }

    /**
     * Get tile from puzzle by coordinate
     * @param x
     * @param y
     * @return
     */
    private int getTile(int x, int y) {
        return this.puzzle.getPuzzle()[y * 9 + x];
    }

    /**
     * Get tile from read only puzzle by coordinate
     * @param x
     * @param y
     * @return
     */
    private int getReadOnlyTile(int x, int y) {
        return this.readOnlyPuzzle.getPuzzle()[y * 9 + x];
    }

    /**
     * set tile to puzzle by coordinate
     * @param x
     * @param y
     * @return
     */
    private void setTile(int x, int y, int value) {
        this.puzzle.getPuzzle()[y * 9 + x] = value;
    }

    private int[] calculateUsedTiles(int x, int y) {
        int c[] = new int[9];
        // horizontal
        for (int i = 0; i < 9; i++) {
            if (i == y) {
                continue;
            }

            int t = getTile(x, i);
            if (t != 0) {
                c[t - 1] = t;
            }
        }

        // vertical
        for (int i = 0; i < 9; i++) {
            if (i == x) {
                continue;
            }

            int t = getTile(i, y);
            if (t != 0) {
                c[t - 1] = t;
            }
        }

        // same cell block
        int startx = (x / 3) * 3;
        int starty = (y / 3) * 3;

        for (int i = startx; i < startx + 3; i++) {
            for (int j = starty; j < starty + 3; j++) {
                if (i == x && j == y) {
                    continue;
                }
                int t = getTile(i, j);
                if (t != 0) {
                    c[t - 1] = t;
                }
            }
        }

        // compress
        int nused = 0;
        for (int t : c) {
            if (t != 0) {
                nused++;
            }
        }

        int c1[] = new int[nused];
        nused = 0;
        for (int t : c) {
            if (t != 0) {
                c1[nused++] = t;
            }
        }
        return c1;
    }
}